﻿Public Module Parser

    Private Const _startOfBlockChar As Char = "{"c
    Private Const _endOfBlockChar As Char = "}"c
    Private Const _inlineValueSplitChar As Char = " "c
    Private Const _keyValueSeparatorChar As Char = "="c

    Private Sub SkipBlock(ByRef stream As IO.StreamReader)

        Dim shouldExit As Boolean = False
        Dim data As LineData

        Do Until shouldExit OrElse stream.EndOfStream
            data = ReadLineData(stream)
            Select Case data.LineType
                Case DataLineType.EndBlockChar
                    shouldExit = True
                Case DataLineType.InLine
                    If data.InLineData.WithEndOfBlock Then shouldExit = True
                Case DataLineType.StartBlock
                    SkipBlock(stream)
            End Select
        Loop

    End Sub

    Public Sub SkipBlock(ByRef stream As IO.StreamReader, blockName As String)

        StartSkipBlock(blockName)
        SkipBlock(stream)
        EndSkipBlock()

    End Sub



    Public Function ReadLineData(ByRef stream As IO.StreamReader) As LineData

        Dim result As LineData
        Dim data As String

        data = stream.ReadLine
        AddReadedLine()
        result = ParseText(data)

        Return result

    End Function

    Public Function ParseText(text As String) As LineData

        Dim result As LineData

        If text.Contains(_keyValueSeparatorChar) Then
            result = ParseKeyValue(text)
        ElseIf text.Trim.Length = 1 Then
            Select Case text.Trim
                Case _startOfBlockChar
                    result = New LineData
                    result.LineType = DataLineType.StarBlockChar
                Case _endOfBlockChar
                    result = New LineData
                    result.LineType = DataLineType.EndBlockChar
                Case Else
                    Throw New InvalidOperationException(String.Format("line <{0}> can't be parsed", text))
            End Select
        Else
            result = ParseInlineData(text)
        End If

        If result Is Nothing Then
            Throw New InvalidOperationException("result is nothing")
        End If
        ' todo use contract
        'Contracts.Contract.Ensures(result IsNot Nothing)

        result.OriginalText = text

        Return result

    End Function

    Private Function ParseInlineData(data As String) As LineData

        Dim result As LineData
        Dim datas As List(Of String)
        Dim withEndBlock As Boolean = False

        datas = data.Trim.Split(_inlineValueSplitChar).ToList()
        If datas(datas.Count - 1) = ControlChars.Tab & _endOfBlockChar OrElse
            datas(datas.Count - 1) = _endOfBlockChar Then
            withEndBlock = True
            datas.RemoveAt(datas.Count - 1)
        End If

        result = New LineData(New InlineData(datas, withEndBlock), data)


        Return result

    End Function

    Private Function ParseKeyValue(line As String) As LineData

        Dim result As LineData
        Dim parts As String() = line.Split(New Char() {_keyValueSeparatorChar})

        If parts.Length = 2 Then
            If parts(1) = String.Empty Then
                result = New LineData(parts(0).Trim, line)
            Else
                result = New LineData(New KeyValueData(parts(0).Trim, RemoveQuotes(parts(1))), line)
            End If
        Else
            Throw New InvalidOperationException(String.Format("Line {0} is not Key/Value or Start Block item"))
        End If

        Return result

    End Function

    Private Function RemoveQuotes(value As String) As String

        Dim result As String

        If value.StartsWith(""""c) AndAlso value.EndsWith(""""c) Then
            result = value.Substring(1, value.Length - 2)
        Else
            result = value
        End If

        Return result

    End Function

    Friend Function DateConverter(value As String) As String

        Dim result As String
        Dim dateParts As String() = value.Split(New Char() {"."c})

        result = String.Format("{0}-{1}-{2}T{3}:00:00Z", dateParts(0), dateParts(1), dateParts(2), Format(dateParts(3), "00"))

        Return result

    End Function

    Public Function CountryConverter(value As String) As CountryShortName

        Dim result As CountryShortName
        Dim success As Boolean

        success = [Enum].TryParse(Of CountryShortName)(value, result)

        If Not success Then
            Throw New InvalidOperationException(String.Format("Unable to parse {0} in {1} enumeration", value, GetType(CountryShortName).ToString))
        End If

        Return result


    End Function

    Public Function ConvertBoolean(value As String) As Boolean

        Dim result As Boolean

        Select Case value.ToLowerInvariant()
            Case "yes"
                result = True
            Case "no"
                result = False
            Case Else
                Throw New InvalidOperationException(String.Format("Unable to convert : '{0}' to Boolean", value))
        End Select

        Return result

    End Function


End Module
